﻿using System;
using System.Collections;
using System.Threading;

namespace Gadgeteer.Modules.Mekalogic
{
	/// <summary>
	/// A CellularRadio module for Microsoft .NET Gadgeteer
	/// </summary>
	public class CellularRadio : Module
	{
		#region PROPERTIES
		private int powerOnDelay;

		/// <summary>
		/// Power on the CellularRadio module.
		/// </summary>
		/// <param name="chargeTimeSeconds">Time, in seconds, to allow the supercapacitor on the CellularRadio module to charge up before turning on. Recommended time is 40 seconds from a cold start.</param>
		public void PowerOn(int chargeTimeSeconds)
		{
			powerOnDelay = chargeTimeSeconds * 1000;

			Thread powerOnThread = new Thread(new ThreadStart(PowerOnSequenceThread));
			powerOnThread.Start();
		}

		/// <summary>
		/// Power on the CellularRadio module.
		/// </summary>
		public void PowerOn()
		{
			TimeSpan t = Gadgeteer.Timer.GetMachineTime();
			if (t.Seconds - 40 < 3) PowerOn(3);
			else PowerOn(t.Seconds - 40);
		}


		private void PowerOnSequenceThread()
		{
			Thread.Sleep(powerOnDelay);
			DebugPrint("Turning ON");
			if (!isPowerOn)
			{
				isPowerOn = true;
				if (!serialLine.IsOpen)
				{
					serialLine.Open();
					Thread.Sleep(100);
					readerThread.Resume();
					Thread.Sleep(100);
				}

				DebugPrint("Turning module on");
				pwrkey.Write(true);
				Thread.Sleep(1200);
				pwrkey.Write(false);
				Thread.Sleep(3000);

				SendATCommand("AT");

				//Set SMS mode to text
				SendATCommand("AT+CMGF=1");
				SendATCommand("AT+CSDH=0");
				// Set the phonebook to be stored in the SIM card
				SendATCommand("AT+CPBS=\"SM\"");
				// Set the sms to be stored in the SIM card
				SendATCommand("AT+CPMS=\"SM\"");
				SendATCommand("AT+CNMI=2,1,0,1,0");
				//// Sets how connected lines are presented
				SendATCommand("AT+COLP=1");
				// Enable GPRS network registration status
				SendATCommand("AT+CGREG=1");
				// Enable GSM network registration status
				SendATCommand("AT+CREG=1");

			}

		}

		/// <summary>
		/// Powers off the Cellular Radio module
		/// </summary>
		public void PowerOff()
		{
			if (isPowerOn)
			{
				pwrkey.Write(true);
				Thread.Sleep(1200);
				pwrkey.Write(false);
				Thread.Sleep(500);
				isPowerOn = false;
				//SendATCommand("AT+CPOWD=1");
			}
		}

		/// <summary>
		/// Time interval in milliseconds to allow the module to initialize 
		/// </summary>
		public int StartupInterval
		{
			get
			{
				return startupInterval;
			}
			set
			{
				startupInterval = value;
			}
		}
		#endregion

		#region PRIVATE ATTRIBUTES
		// Serial line that sends commands to the GSM module
		private Interfaces.Serial serialLine;
		// Serial reader thread
		private Thread readerThread;
		// Tells whether the module is on
		private bool isPowerOn = false;
		// Power line to the GSM module
		private Interfaces.DigitalOutput pwrkey;
		private Socket socket;
		private Queue newMessages = new Queue();
		private Queue requestedMessages = new Queue();
		private bool isModuleBusy = false;
		private int startupInterval = 40000;
		#endregion

		#region ENUM DICTIONARY
		/// <summary>
		/// Possible retured states by methods
		/// </summary>
		public enum ReturnedState
		{
			/// <summary>
			/// Operation successful
			/// </summary>
			OK,
			/// <summary>
			/// Error in the operation. See method documentation.
			/// </summary>
			Error,
			/// <summary>
			/// Cellular Radio Module is off
			/// </summary>
			ModuleIsOff,
			/// <summary>
			/// Command syntax is incorrect
			/// </summary>
			InvalidCommand,
			/// <summary>
			/// Module is busy
			/// </summary>
			ModuleBusy

		}
		/// <summary>
		/// Possible states of network registration
		/// </summary>
		public enum NetworkRegistrationState
		{
			/// <summary>
			/// Module couldn't find a network
			/// </summary>
			NotSearching,
			/// <summary>
			/// Module is registered to a network
			/// </summary>
			Registered,
			/// <summary>
			/// Module is searching for a network
			/// </summary>
			Searching,
			/// <summary>
			/// Module tried to register to a network, but it was denied
			/// </summary>
			RegistrationDenied,
			/// <summary>
			/// Unknown
			/// </summary>
			Unknown,
			/// <summary>
			/// Roaming
			/// </summary>
			Roaming,
			/// <summary>
			/// Error
			/// </summary>
			Error
		}

		/// <summary>
		/// Possible states of the SIM card
		/// </summary>
		public enum PINState
		{
			/// <summary>
			/// SIM is unlocked and ready to be used.
			/// </summary>
			Ready,
			/// <summary>
			/// SIM is locked waiting for the PIN
			/// </summary>
			PIN,
			/// <summary>
			/// SIM is locked waiting for the PUK
			/// </summary>
			PUK,
			/// <summary>
			/// SIM is waiting for phone to SIM card (antitheft) 
			/// </summary>
			PH_PIN,// ----> Not sure of how this works
			/// <summary>
			/// SIM is waiting for phone to SIM PUK (antitheft) 
			/// </summary>
			PH_PUK, // ----> Not sure of how this works
			/// <summary>
			/// SIM is waiting for second PIN
			/// </summary>
			PIN2,
			/// <summary>
			/// SIM is waiting for second PUK
			/// </summary>
			PUK2,
			/// <summary>
			/// SIM is not present
			/// </summary>
			NotPresent
		}

		/// <summary>
		/// Possible states for a text message
		/// </summary>
		public enum SmsState
		{
			/// <summary>
			/// Messages that were received and read
			/// </summary>
			ReceivedUnread,
			/// <summary>
			/// Messages that were received but not yet read
			/// </summary>
			ReceivedRead,
			/// <summary>
			/// Messages that were created but not yet sent
			/// </summary>
			StoredUnsent,
			/// <summary>
			/// Messages that were created and sent
			/// </summary>
			StoredSent,
			/// <summary>
			/// All messages
			/// </summary>
			All
		}

		/// <summary>
		/// Possible states of a call
		/// </summary>
		public enum PhoneActivityType
		{
			/// <summary>
			/// Phone is not calling or being called
			/// </summary>
			Ready,
			/// <summary>
			/// Ringer is active
			/// </summary>
			Ringing,
			/// <summary>
			/// Active voice all
			/// </summary>
			CallInProgress,
			/// <summary>
			/// Module is not guaranteed to respond.
			/// </summary>
			Unknown,
			/// <summary>
			/// Communication line with the Cellular Radio Module is busy
			/// </summary>
			CommLineBusy
		}

		/// <summary>
		/// Possible values of the strength of a signal
		/// </summary>
		public enum SignalStrengthType
		{
			/// <summary>
			/// -115dBm or less
			/// </summary>
			VeryWeak,
			/// <summary>
			/// -111dBm
			/// </summary>
			Weak,
			/// <summary>
			/// -110..-54dBm
			/// </summary>
			Strong,
			/// <summary>
			/// -52dBm or greater
			/// </summary>
			VeryStrong,
			/// <summary>
			/// Not known or undetectable
			/// </summary>
			Unknown,
			/// <summary>
			/// Error in the response from the GSM Module
			/// </summary>
			Error
		}
		/// <summary>
		/// Reasons for a call to be ended
		/// </summary>
		public enum CallEndType
		{
			/// <summary>
			/// No dial tone was found
			/// </summary>
			NoDialTone,
			/// <summary>
			/// No carrier was found
			/// </summary>
			NoCarrier,
			/// <summary>
			/// Line is busy
			/// </summary>
			Busy
		}
		#endregion

		#region CONSTRUCTOR
		/// <summary>Instantiates a Cellular Radio Module</summary>
		/// <param name="socketNumber">The socket that this module is plugged in to.</param>
		public CellularRadio(int socketNumber)
		{
			socket = Socket.GetSocket(socketNumber, true, this, null);
			pwrkey = new Interfaces.DigitalOutput(socket, Socket.Pin.Three, false, this);
			serialLine = new Interfaces.Serial(socket, 19200, Interfaces.Serial.SerialParity.None, Interfaces.Serial.SerialStopBits.One, 8, Interfaces.Serial.HardwareFlowControl.Required, this);

			serialLine.Open();
			serialLine.Write("AT");
			Thread.Sleep(1000);
			String response = "";
			while (serialLine.BytesToRead > 0)
			{
				response += (char)serialLine.ReadByte();
			}
			DebugPrint("RESP: " + response);
			if (response.Length != 0)
			{
				DebugPrint("Sending off pulse");
				pwrkey.Write(true);
				Thread.Sleep(1200);
				pwrkey.Write(false);
				Thread.Sleep(500);
			}
			isPowerOn = false;

			readerThread = new Thread(new ThreadStart(SerialRead));
			readerThread.Start();
		}

		#endregion

		#region COMMUNICATION

		/// <summary>
		/// Sends an AT command to the Cellular Radio Module. It automatically appends the carriage return.
		/// </summary>
		/// <param name="atCommand">String containing the AT command. See SIM900_ATC_V1.00 for reference.</param>
		/// <returns>
		/// ReturnedState.OK - Command was sent
		/// ReturnedState.ModuleIsOff - Module is off
		/// ReturnedState.Error - Serial line is not open
		/// ReturnedState.InvalidCommand - Invalid AT command
		/// </returns>
		public ReturnedState SendATCommand(string atCommand)
		{
			// Check if module is busy
			if (isModuleBusy) return ReturnedState.ModuleBusy;

			// Append carriage return
			if (atCommand.IndexOf("\r") < 0) atCommand += "\r";

			// Check if string is an AT command
			if (atCommand.IndexOf("AT") < 0) return ReturnedState.InvalidCommand;

			// Check if module is on
			if (!isPowerOn) return ReturnedState.ModuleIsOff;

			// Check if serial line is open
			if (serialLine.IsOpen) serialLine.Write(atCommand);
			else return ReturnedState.Error;
			Thread.Sleep(100);
			DebugPrint("SENT: " + atCommand);
			return ReturnedState.OK;
		}
		#endregion

		/// <summary>
		/// Sends Raw data to the Cellular Radio Module.  The SIM900 sends the data automatically on a timeout (see TCP/IP Manual).
		/// </summary>
		/// <param name="data">String containing the data to send</param>
		/// <returns>
		/// ReturnedState.OK - Data was sent
		/// ReturnedState.ModuleIsOff - Module is off
		/// ReturnedState.Error - Serial line is not open
		/// </returns>
		public ReturnedState SendRawTcpData(string data)
		{
			// Check if module is busy
			if (isModuleBusy) return ReturnedState.ModuleBusy;

			// Check if module is on
			if (!isPowerOn) return ReturnedState.ModuleIsOff;

			// Check if serial line is open
			if (serialLine.IsOpen) serialLine.Write(data);
			else return ReturnedState.Error;
			// wait until buffer is empty
			while (serialLine.BytesToWrite > 0)
			{
				Thread.Sleep(50);
			}
			//Thread.Sleep(1000);
			DebugPrint("SENT DATA: " + data);
			return ReturnedState.OK;
		}

		/// <summary>
		/// Send a byte[] array over TCP Connection
		/// </summary>
		/// <param name="data">Array of bytes to send</param>
		/// <returns>
		/// ReturnedState.OK - Data was sent
		/// ReturnedState.ModuleIsOff - Module is off
		/// ReturnedState.Error - Serial line is not open
		///</returns>
		[Obsolete("Untested")]
		public ReturnedState SendRawTcpData(byte[] data)
		{
			// Check if module is busy
			if (isModuleBusy) return ReturnedState.ModuleBusy;

			// Check if module is on
			if (!isPowerOn) return ReturnedState.ModuleIsOff;

			// Check if serial line is open
			if (serialLine.IsOpen) serialLine.Write(data);
			else return ReturnedState.Error;
			// wait until buffer is empty
			while (serialLine.BytesToWrite > 0)
			{
				Thread.Sleep(50);
			}
			DebugPrint("SENT DATA::AS BYTES");
			return ReturnedState.OK;
		}

		private string ReadAllFromSerial()
		{
			string response = "";
			while (serialLine.BytesToRead > 0)
			{
				response += (char)serialLine.ReadByte();
			}
			return response;
		}

		/// <summary>
		/// When true the radio acts lke a modem and does not attempt to parse AT commands from the received data stream
		/// </summary>
		public bool IsInDataMode { get; set; }
		private void ProcessDataMode(string response)
		{
			if (response.Length > 0)
			{
				OnTcpDataReceived(this, response);
				DebugPrint(response);
			}
		}

		#region SERIAL READER THREAD
		private void SerialRead()
		{
			while (true)
			{
				if (serialLine.IsOpen)
				{
					string response = ReadAllFromSerial();

					if (IsInDataMode)
						ProcessDataMode(response);
					else
						ProcessATCommandResponse(response);
				}
				Thread.Sleep(100);
			}
		}

		// ReSharper disable InconsistentNaming
		private void ProcessATCommandResponse(string response)
		// ReSharper restore InconsistentNaming
		{
			if (response.Length > 0) DebugPrint("<" + response + ">");

			//Response Parsing
			int first;
			int last;
			string reply;

			#region CHECK TCP CONNECT RESULT
			if (response.IndexOf("FAIL") > 0 ||
				response.IndexOf("ERROR") > 0)
			{
				_isTcpConnectionAtResultReceived = true;
				_tcpConnectResultSuccess = false;
			}
			else if (response.IndexOf("CONNECT") > 0)
			{
				_isTcpConnectionAtResultReceived = true;
				_tcpConnectResultSuccess = true;
			}
			#endregion

			#region Check Pin State (CPIN)

			if (response.IndexOf("+CPIN:") > 0)
			{
				first = response.IndexOf("+CPIN:");
				last = response.IndexOf("\n", first);
				reply = (response.Substring(first, last - first)).Trim();

				if (reply.IndexOf("READY") > -1)
				{
					OnPinStateRetrieved(this, PINState.Ready);
				}
				else if (reply.IndexOf("SIM PIN2") > -1)
				{
					OnPinStateRetrieved(this, PINState.PIN2);
				}
				else if (reply.IndexOf("SIM PUK2") > -1)
				{
					OnPinStateRetrieved(this, PINState.PUK2);
				}
				else if (reply.IndexOf("PH_SIM PIN") > -1)
				{
					OnPinStateRetrieved(this, PINState.PH_PIN);
				}
				else if (reply.IndexOf("PH_SIM PUK") > -1)
				{
					OnPinStateRetrieved(this, PINState.PH_PUK);
				}
				else if (reply.IndexOf("SIM PIN") > -1)
				{
					OnPinStateRetrieved(this, PINState.PIN);
				}
				else if (reply.IndexOf("SIM PUK") > -1)
				{
					OnPinStateRetrieved(this, PINState.PUK);
				}
				else
				{
					OnPinStateRetrieved(this, PINState.NotPresent);
				}
			}

			#endregion

			#region Check GSM Network Registration (CREG)

			if (response.IndexOf("+CREG:") > 0)
			{
				string cmd = "+CREG:";
				first = response.IndexOf(cmd) + cmd.Length;
				last = response.IndexOf("\n", first);
				reply = (response.Substring(first, last - first)).Trim();

				try
				{
					int state = int.Parse(reply);
					switch (state)
					{
						case 0:
							OnGsmNetworkRegistrationChanged(this, NetworkRegistrationState.NotSearching);
							break;
						case 1:
							OnGsmNetworkRegistrationChanged(this, NetworkRegistrationState.Registered);
							break;
						case 2:
							OnGsmNetworkRegistrationChanged(this, NetworkRegistrationState.Searching);
							break;
						case 3:
							OnGsmNetworkRegistrationChanged(this, NetworkRegistrationState.RegistrationDenied);
							break;
						case 4:
							OnGsmNetworkRegistrationChanged(this, NetworkRegistrationState.Unknown);
							break;
						case 5:
							OnGsmNetworkRegistrationChanged(this, NetworkRegistrationState.Roaming);
							break;
					}
				}
				catch (Exception)
				{
					OnGsmNetworkRegistrationChanged(this, NetworkRegistrationState.Error);
				}
			}

			#endregion

			#region Check GPRS Network Registration (CGREG)

			if (response.IndexOf("+CGREG:") > 0)
			{
				string cmd = "+CGREG:";

				first = response.IndexOf(cmd) + cmd.Length;
				last = response.IndexOf("\n", first);
				reply = (response.Substring(first, last - first)).Trim();
				try
				{
					int state = int.Parse(reply);
					switch (state)
					{
						case 0:
							OnGprsNetworkRegistrationChanged(this, NetworkRegistrationState.NotSearching);
							break;
						case 1:
							OnGprsNetworkRegistrationChanged(this, NetworkRegistrationState.Registered);
							break;
						case 2:
							OnGprsNetworkRegistrationChanged(this, NetworkRegistrationState.Searching);
							break;
						case 3:
							OnGprsNetworkRegistrationChanged(this, NetworkRegistrationState.RegistrationDenied);
							break;
						case 4:
							OnGprsNetworkRegistrationChanged(this, NetworkRegistrationState.Unknown);
							break;
						case 5:
							OnGprsNetworkRegistrationChanged(this, NetworkRegistrationState.Roaming);
							break;
					}
				}
				catch (Exception)
				{
					DebugPrint("CGREG ERROR");
				}
			}

			#endregion

			#region Check Sms Received (CMTI)

			if (response.IndexOf("+CMTI:") > 0)
			{
				string cmd = "+CMTI:";
				first = response.IndexOf(cmd) + cmd.Length;
				last = response.IndexOf("\n", first);
				reply = (response.Substring(first, last - first)).Trim();
				char[] sep = { ',' };
				string[] split = reply.Split(sep);

				try
				{
					int position = int.Parse(split[1]);
					newMessages.Enqueue(position);
					RetrieveSms(position, false);
				}
				catch (Exception)
				{
					DebugPrint("ERROR");
				}
			}

			#endregion

			#region Check Incoming Call (CLIP)

			if (response.IndexOf("+CLIP:") > 0)
			{
				string cmd = "+CLIP:";
				first = response.IndexOf(cmd) + cmd.Length;
				last = response.IndexOf("\n", first);
				reply = (response.Substring(first, last - first)).Trim();
				char[] sep = { ',' };
				string[] split = reply.Split(sep);
				if (split.Length > 1)
				{
					OnIncomingCall(this, split[0].Trim('\"'));
				}
			}

			#endregion

			#region Check Sms Retrieved (CMGR)

			if (response.IndexOf("+CMGR:") > 0)
			{
				string cmd = "+CMGR:";
				first = response.IndexOf(cmd) + cmd.Length;
				int mid = response.IndexOf("\n", first);
				last = response.IndexOf("OK", mid);

				Sms sms = new Sms();
				// Get message
				sms.TextMessage = (response.Substring(mid, last - mid)).Trim();


				reply = (response.Substring(first, mid - first)).Trim();
				char[] sep = { ',' };
				string[] split = reply.Split(sep);

				if (split.Length == 5)
				{
					// Get number
					sms.TelephoneNumber = split[1].Trim('\"');

					// Get status
					if (split[0].IndexOf("REC UNREAD") > 0)
					{
						sms.Status = SmsState.ReceivedUnread;
					}
					else if (split[0].IndexOf("REC READ") > 0)
					{
						sms.Status = SmsState.ReceivedRead;
					}
					else if (split[0].IndexOf("STO UNSENT") > 0)
					{
						sms.Status = SmsState.StoredUnsent;
					}
					else if (split[0].IndexOf("STO SENT") > 0)
					{
						sms.Status = SmsState.StoredSent;
					}
					else
					{
						// ERROR
						sms.Status = SmsState.All;
					}

					// Get timestamp
					try
					{
						if ((split[3].Length < 7) || (split[4].Length < 7))
						{
							sms.Timestamp = new DateTime();
						}
						else
						{
							DateTime timestamp = new DateTime(int.Parse(split[3].Substring(1, 2)) + 2000, //Year
															  int.Parse(split[3].Substring(4, 2)), //Month
															  int.Parse(split[3].Substring(7, 2)), // Day
															  int.Parse(split[4].Substring(0, 2)), // Hour
															  int.Parse(split[4].Substring(3, 2)), // Minute
															  int.Parse(split[4].Substring(6, 2))); // Second
							sms.Timestamp = timestamp;
							int reqIndex = (int)requestedMessages.Dequeue();
							DebugPrint("REQ: " + reqIndex);
							sms.Index = reqIndex;
							if (newMessages.Contains(reqIndex))
							{
								newMessages.Dequeue();
								OnSmsReceived(this, sms);
							}
							else
							{
								OnSmsRetrieved(this, sms);
							}
						}
					}
					catch (Exception)
					{
					}
				}
			}

			#endregion

			#region Check Phone Activity (CPAS)

			if (response.IndexOf("+CPAS:") > 0)
			{
				string atCommand = "+CPAS:";
				first = response.IndexOf(atCommand) + atCommand.Length;
				last = response.IndexOf("\n", first);
				reply = (response.Substring(first, last - first)).Trim();
				DebugPrint("R:[" + reply + "]");
				try
				{
					int status = int.Parse(reply);
					switch (status)
					{
						case 0:
							OnPhoneActivityRetrieved(this, PhoneActivityType.Ready);
							break;
						case 2:
							OnPhoneActivityRetrieved(this, PhoneActivityType.Unknown);
							break;
						case 3:
							OnPhoneActivityRetrieved(this, PhoneActivityType.Ringing);
							break;
						case 4:
							OnPhoneActivityRetrieved(this, PhoneActivityType.CallInProgress);
							break;
					}
				}
				catch (Exception)
				{
					DebugPrint("Error");
				}
			}

			#endregion

			#region Check Retrieve Contact (CPBR)

			if (response.IndexOf("+CPBR:") > 0)
			{
				string cmd = "+CPBR:";
				first = response.IndexOf(cmd) + cmd.Length;
				last = response.IndexOf("\n", first);
				reply = (response.Substring(first, last - first)).Trim();
				char[] sep = { ',' };
				string[] split = reply.Split(sep);

				try
				{
					Contact contact = new Contact(split[1].Trim('\"'), split[3].Trim('\"'));
					OnContactRetrieved(this, contact);
				}
				catch (Exception)
				{
					DebugPrint("ERROR");
					OnSmsReceived(this, null);
				}
			}

			#endregion

			#region Check Retrieve Clock (CCLK)

			if (response.IndexOf("+CCLK:") > 0)
			{
				string atCommand = "+CCLK:";
				first = response.IndexOf(atCommand) + atCommand.Length;
				last = response.IndexOf("OK", first);

				string s = (response.Substring(first, last - first)).Trim();
				try
				{
					DateTime ret = new DateTime(int.Parse(s.Substring(1, 2)) + 2000,
												int.Parse(s.Substring(4, 2)),
												int.Parse(s.Substring(7, 2)),
												int.Parse(s.Substring(10, 2)),
												int.Parse(s.Substring(13, 2)),
												int.Parse(s.Substring(16, 2)));
					OnClockRetrieved(this, ret);
				}
				catch (Exception)
				{
					DebugPrint("Clock Error");
				}
			}

			#endregion

			#region Check Retrieve IMEI (GSN)

			if (response.IndexOf("GSN") > 0)
			{
				string atCommand = "GSN";
				first = response.IndexOf(atCommand) + atCommand.Length;
				last = response.IndexOf("OK", first);
				reply = (response.Substring(first, last - first)).Trim();
				OnImeiRetrieved(this, reply);
			}

			#endregion

			#region Check Retrieve Signal Strength (CSQ)

			if (response.IndexOf("+CSQ:") > 0)
			{
				string atCommand = "+CSQ:";

				//String parsing
				// Return format: +CSQ: <rssi>,<ber>
				first = response.IndexOf(atCommand) + atCommand.Length;
				last = response.IndexOf("OK", first);

				reply = (response.Substring(first, last - first)).Trim();
				char[] sep = new char[1];
				sep[0] = ',';
				//DebugPrint("R: " + s);
				String[] split = reply.Split(sep);
				if (split.Length == 2)
				{
					try
					{
						int signal = int.Parse(split[0]);
						switch (signal)
						{
							case 0:
								OnSignalStrengthRetrieved(this, SignalStrengthType.VeryWeak);
								break;
							case 1:
								OnSignalStrengthRetrieved(this, SignalStrengthType.Weak);
								break;
							case 31:
								OnSignalStrengthRetrieved(this, SignalStrengthType.VeryStrong);
								break;
							case 99:
								OnSignalStrengthRetrieved(this, SignalStrengthType.Unknown);
								break;
							default:
								if (signal >= 2 && signal <= 30) OnSignalStrengthRetrieved(this, SignalStrengthType.Strong);
								break;
						}
					}
					catch (Exception)
					{
						DebugPrint("Signal Strength Error");
					}
				}
			}

			#endregion

			#region Check Retrieve Operator (COPS)

			if (response.IndexOf("+COPS:") > 0)
			{
				string atCommand = "+COPS:";

				//String parsing  
				// Return format: +COPS:<mode>[,<format>,<oper>]
				first = response.IndexOf(atCommand) + atCommand.Length;
				last = response.IndexOf("OK", first);
				reply = (response.Substring(first, last - first)).Trim();
				char[] sep = new char[1];
				sep[0] = ',';
				String[] split = reply.Split(sep);
				if (split.Length == 3)
				{
					OnOperatorRetrieved(this, split[2]);
				}
				else
				{
					OnOperatorRetrieved(this, null);
				}
			}

			#endregion

			#region Check Power On Error (NORMAL POWER DOWN)

			if (response.IndexOf("NORMAL POWER DOWN") > 0)
			{
				if (isPowerOn)
				{
					Reset();
				}
			}

			#endregion

			#region Check Retrieve SMS List (CMGL)

			if (response.IndexOf("+CMGL:") > 0)
			{
				first = 0;
				string cmd = "+CMGL:";
				Sms sms;
				int mid;
				ArrayList smsList = new ArrayList();
				char[] sep = { ',' };
				do
				{
					first = response.IndexOf(cmd, first) + cmd.Length;
					mid = response.IndexOf("\n", first);
					last = response.IndexOf("\n", mid + 1);
					reply = (response.Substring(first, mid - first)).Trim();
					string[] split = reply.Split(sep);

					sms = new Sms();
					if (split.Length == 6)
					{
						// Get index
						try
						{
							sms.Index = int.Parse(split[0]);
						}
						catch (Exception)
						{
						}
						;
						// Get message
						sms.TextMessage = (response.Substring(mid, last - mid)).Trim();
						// Get number
						sms.TelephoneNumber = split[2].Trim('\"');

						// Get status
						if (split[1].IndexOf("REC UNREAD") > 0)
						{
							sms.Status = SmsState.ReceivedUnread;
						}
						else if (split[1].IndexOf("REC READ") > 0)
						{
							sms.Status = SmsState.ReceivedRead;
						}
						else if (split[1].IndexOf("STO UNSENT") > 0)
						{
							sms.Status = SmsState.StoredUnsent;
						}
						else if (split[1].IndexOf("STO SENT") > 0)
						{
							sms.Status = SmsState.StoredSent;
						}
						else
						{
							// ERROR
							sms.Status = SmsState.All;
						}

						// Get timestamp
						try
						{
							if ((split[4].Length < 7) || (split[5].Length < 7))
							{
								sms.Timestamp = new DateTime();
							}
							else
							{
								DateTime timestamp = new DateTime(int.Parse(split[4].Substring(1, 2)) + 2000, //Year
																  int.Parse(split[4].Substring(4, 2)), //Month
																  int.Parse(split[4].Substring(7, 2)), // Day
																  int.Parse(split[5].Substring(0, 2)), // Hour
																  int.Parse(split[5].Substring(3, 2)), // Minute
																  int.Parse(split[5].Substring(6, 2))); // Second
								sms.Timestamp = timestamp;
								smsList.Add(sms);
							}
						}
						catch (Exception)
						{
						}
					}
				} while (response.IndexOf("+CMGL", mid) > 0);
				OnSmsListRetrieved(this, smsList);
			}

			#endregion

			#region Check No Carrier (NO CARRIER)

			if (response.IndexOf("NO CARRIER") > 0)
			{
				OnCallEnded(this, CallEndType.NoCarrier);
				if (isModuleBusy) isModuleBusy = false;
			}

			#endregion

			#region Check No Dial Tone (NO DIALTONE)

			if (response.IndexOf("NO DIALTONE") > 0)
			{
				OnCallEnded(this, CallEndType.NoDialTone);
				if (isModuleBusy) isModuleBusy = false;
			}

			#endregion

			#region Check Busy (BUSY)

			if (response.IndexOf("BUSY") > 0)
			{
				OnCallEnded(this, CallEndType.Busy);
				if (isModuleBusy) isModuleBusy = false;
			}

			#endregion

			#region Check Call Connected (COLP)

			if (response.IndexOf("+COLP:") > 0)
			{
				if (isModuleBusy) isModuleBusy = false;
				string atCommand = "+COLP";

				//String parsing  
				// Return format: +COPS:<mode>[,<format>,<oper>]
				first = response.IndexOf(atCommand) + atCommand.Length;
				last = response.IndexOf("\n", first);
				reply = (response.Substring(first, last - first)).Trim();
				char[] sep = new char[1];
				sep[0] = ',';
				String[] split = reply.Split(sep);
				if (split.Length == 5)
				{
					OnCallConnected(this, split[0].Trim('\"'));
				}
			}

			#endregion

			#region Check Module Initialization (Call Ready)

			if (response.IndexOf("Call Ready") >= 0)
			{
				OnModuleInitialized(this);
			}

			#endregion

			#region Check GPRS Attached (CISFR)

			if (response.IndexOf("CIFSR") > 0)
			{
				string atCommand = "AT+CIFSR\n\n\n";
				if (response.IndexOf("ERROR") < 0)
				{
					first = response.IndexOf(atCommand) + atCommand.Length;
					last = response.IndexOf("\n", first + 1);
					reply = (response.Substring(first, last - first)).Trim();
					OnGprsAttached(this, reply);
				}
			}

			#endregion

			#region Check SendSms Result

			if (response.IndexOf("+CMGS:") >= 0)
			{
				if (isModuleBusy) isModuleBusy = false;
			}
			if ((response.IndexOf("ERROR") >= 0) && (isModuleBusy))
			{
				isModuleBusy = false;
			}

			#endregion
		}

		#endregion

		#region DELEGATES AND EVENTS
		#region Pin State Retrieved
		/// <summary>
		/// Represents the delegate used for the <see cref="PinStateRetrieved"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>
		/// <param name="pinState">Current state of the PIN</param>
		public delegate void PinStateRetrievedHandler(CellularRadio sender, PINState pinState);
		/// <summary>
		/// Event raised by the <see cref="RetrievePinState"/> method.
		/// </summary>
		public event CellularRadio.PinStateRetrievedHandler PinStateRetrieved;
		private CellularRadio.PinStateRetrievedHandler onPinStateRetrieved;

		/// <summary>
		/// Raises the <see cref="PinStateRetrieved"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>  
		/// <param name="pinState">Current state of the PIN</param>
		protected virtual void OnPinStateRetrieved(CellularRadio sender, PINState pinState)
		{
			if (onPinStateRetrieved == null) onPinStateRetrieved = new CellularRadio.PinStateRetrievedHandler(OnPinStateRetrieved);
			if (Program.CheckAndInvoke(PinStateRetrieved, onPinStateRetrieved, sender, pinState))
			{
				PinStateRetrieved(sender, pinState);
			}
		}
		#endregion

		#region GSM Network Registration Changed
		/// <summary>
		/// Represents the delegate used for the <see cref="GsmNetworkRegistrationChanged"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>
		/// <param name="networkState">Current state of the GSM network registration</param>
		public delegate void GsmNetworkRegistrationChangedHandler(CellularRadio sender, NetworkRegistrationState networkState);
		/// <summary>
		/// Event raised when the module emits a network registration message.
		/// </summary>
		public event CellularRadio.GsmNetworkRegistrationChangedHandler GsmNetworkRegistrationChanged;
		private CellularRadio.GsmNetworkRegistrationChangedHandler onGsmNetworkRegistrationChanged;

		/// <summary>
		/// Raises the <see cref="GsmNetworkRegistrationChanged"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>  
		/// <param name="networkState">Current state of the GSM network registration</param>
		protected virtual void OnGsmNetworkRegistrationChanged(CellularRadio sender, NetworkRegistrationState networkState)
		{
			if (onGsmNetworkRegistrationChanged == null) onGsmNetworkRegistrationChanged = new CellularRadio.GsmNetworkRegistrationChangedHandler(OnGsmNetworkRegistrationChanged);
			if (Program.CheckAndInvoke(GsmNetworkRegistrationChanged, onGsmNetworkRegistrationChanged, sender, networkState))
			{
				GsmNetworkRegistrationChanged(sender, networkState);
			}
		}
		#endregion

		#region GPRS Network Registration Changed
		/// <summary>
		/// Represents the delegate used for the <see cref="GprsNetworkRegistrationChanged"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>
		/// <param name="networkState">Current state of the GPRS network registration</param>
		public delegate void GprsNetworkRegistrationChangedHandler(CellularRadio sender, NetworkRegistrationState networkState);
		/// <summary>
		/// Event raised when the module emits a network registration message.
		/// </summary>
		public event CellularRadio.GprsNetworkRegistrationChangedHandler GprsNetworkRegistrationChanged;
		private CellularRadio.GprsNetworkRegistrationChangedHandler onGprsNetworkRegistrationChanged;

		/// <summary>
		/// Raises the <see cref="GprsNetworkRegistrationChanged"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>  
		/// <param name="networkState">Current state of the GPRS network registration</param>
		protected virtual void OnGprsNetworkRegistrationChanged(CellularRadio sender, NetworkRegistrationState networkState)
		{
			if (onGprsNetworkRegistrationChanged == null) onGprsNetworkRegistrationChanged = new CellularRadio.GprsNetworkRegistrationChangedHandler(OnGprsNetworkRegistrationChanged);
			if (Program.CheckAndInvoke(GprsNetworkRegistrationChanged, onGprsNetworkRegistrationChanged, sender, networkState))
			{
				GprsNetworkRegistrationChanged(sender, networkState);
			}
		}
		#endregion

		#region Sms Received
		/// <summary>
		/// Represents the delegate used for the <see cref="SmsReceived"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>
		/// <param name="message">Object containing the SMS message</param>
		public delegate void SmsReceivedHandler(CellularRadio sender, Sms message);
		/// <summary>
		/// Event raised when the module receives a new SMS message.
		/// </summary>
		public event CellularRadio.SmsReceivedHandler SmsReceived;
		private CellularRadio.SmsReceivedHandler onSmsReceived;

		/// <summary>
		/// Raises the <see cref="SmsReceived"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>  
		/// <param name="message">Object containing the SMS message</param>
		protected virtual void OnSmsReceived(CellularRadio sender, Sms message)
		{
			if (onSmsReceived == null) onSmsReceived = new CellularRadio.SmsReceivedHandler(OnSmsReceived);
			if (Program.CheckAndInvoke(SmsReceived, onSmsReceived, sender, message))
			{
				SmsReceived(sender, message);
			}
		}
		#endregion

		#region Incoming Call
		/// <summary>
		/// Represents the delegate used for the <see cref="IncomingCall"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>
		/// <param name="caller">Number of the caller</param>
		public delegate void IncomingCallHandler(CellularRadio sender, string caller);
		/// <summary>
		/// Event raised when the module detects an incoming call.
		/// </summary>
		public event CellularRadio.IncomingCallHandler IncomingCall;
		private CellularRadio.IncomingCallHandler onIncomingCall;

		/// <summary>
		/// Raises the <see cref="IncomingCall"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>  
		/// <param name="caller">Number of the caller</param>
		protected virtual void OnIncomingCall(CellularRadio sender, string caller)
		{
			if (onIncomingCall == null) onIncomingCall = new CellularRadio.IncomingCallHandler(IncomingCall);
			if (Program.CheckAndInvoke(IncomingCall, onIncomingCall, sender, caller))
			{
				IncomingCall(sender, caller);
			}
		}
		#endregion

		#region SMS Retrieved
		/// <summary>
		/// Represents the delegate used for the <see cref="SmsRetrieved"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>
		/// <param name="message">SMS message that was requested</param>
		public delegate void SmsRetrievedHandler(CellularRadio sender, Sms message);
		/// <summary>
		/// Event raised when the module detects an incoming call.
		/// </summary>
		public event CellularRadio.SmsRetrievedHandler SmsRetrieved;
		private CellularRadio.SmsRetrievedHandler onSmsRetrieved;

		/// <summary>
		/// Raises the <see cref="SmsRetrieved"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>  
		/// <param name="message">SMS message that was requested</param>
		protected virtual void OnSmsRetrieved(CellularRadio sender, Sms message)
		{
			if (onSmsRetrieved == null) onSmsRetrieved = new CellularRadio.SmsRetrievedHandler(SmsRetrieved);
			if (Program.CheckAndInvoke(SmsRetrieved, onSmsRetrieved, sender, message))
			{
				SmsRetrieved(sender, message);
			}
		}
		#endregion

		#region Phone Activity Retrieved
		/// <summary>
		/// Represents the delegate used for the <see cref="PhoneActivityRetrieved"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>
		/// <param name="activity">Current activity in which the phone is engaged</param>
		public delegate void PhoneActivityRetrievedHandler(CellularRadio sender, PhoneActivityType activity);
		/// <summary>
		/// Event raised when the module receives a phone activity message.
		/// </summary>
		public event CellularRadio.PhoneActivityRetrievedHandler PhoneActivityRetrieved;
		private CellularRadio.PhoneActivityRetrievedHandler onPhoneActivityRetrieved;

		/// <summary>
		/// Raises the <see cref="PhoneActivityRetrieved"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>  
		/// <param name="activity">Current activity in which the phone is engaged</param>
		protected virtual void OnPhoneActivityRetrieved(CellularRadio sender, PhoneActivityType activity)
		{
			if (onPhoneActivityRetrieved == null) onPhoneActivityRetrieved = new CellularRadio.PhoneActivityRetrievedHandler(PhoneActivityRetrieved);
			if (Program.CheckAndInvoke(PhoneActivityRetrieved, onPhoneActivityRetrieved, sender, activity))
			{
				PhoneActivityRetrieved(sender, activity);
			}
		}
		#endregion

		#region Contact Retrieved
		/// <summary>
		/// Represents the delegate used for the <see cref="ContactRetrieved"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>
		/// <param name="contact">Contact object with the requested phonebook entry</param>
		public delegate void ContactOpenRetrieved(CellularRadio sender, Contact contact);
		/// <summary>
		/// Event raised by the <see cref="RetrieveContact"/> method
		/// </summary>
		public event CellularRadio.ContactOpenRetrieved ContactRetrieved;
		private CellularRadio.ContactOpenRetrieved onContactRetrieved;

		/// <summary>
		/// Raises the <see cref="ContactRetrieved"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>  
		/// <param name="contact">Contact object with the requested phonebook entry</param>
		protected virtual void OnContactRetrieved(CellularRadio sender, Contact contact)
		{
			if (onContactRetrieved == null) onContactRetrieved = new CellularRadio.ContactOpenRetrieved(ContactRetrieved);
			if (Program.CheckAndInvoke(ContactRetrieved, onContactRetrieved, sender, contact))
			{
				ContactRetrieved(sender, contact);
			}
		}
		#endregion

		#region Clock Retrieved
		/// <summary>
		/// Represents the delegate used for the <see cref="ClockRetrieved"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>
		/// <param name="clock">Module's date and time</param>
		public delegate void ClockRetrievedHandler(CellularRadio sender, DateTime clock);
		/// <summary>
		/// Event raised when the module receives a phone activity message.
		/// </summary>
		public event CellularRadio.ClockRetrievedHandler ClockRetrieved;
		private CellularRadio.ClockRetrievedHandler onClockRetrieved;

		/// <summary>
		/// Raises the <see cref="ClockRetrieved"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>  
		/// <param name="clock">Module's date and time</param>
		protected virtual void OnClockRetrieved(CellularRadio sender, DateTime clock)
		{
			if (onClockRetrieved == null) onClockRetrieved = new CellularRadio.ClockRetrievedHandler(ClockRetrieved);
			if (Program.CheckAndInvoke(ClockRetrieved, onClockRetrieved, sender, clock))
			{
				ClockRetrieved(sender, clock);
			}
		}
		#endregion

		#region IMEI Retrieved
		/// <summary>
		/// Represents the delegate used for the <see cref="ImeiRetrieved"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>
		/// <param name="imei">Module's International Mobile Equipment Identification number</param>
		public delegate void ImeiRetrievedHandler(CellularRadio sender, string imei);
		/// <summary>
		/// Event raised when the module receives a phone activity message.
		/// </summary>
		public event CellularRadio.ImeiRetrievedHandler ImeiRetrieved;
		private CellularRadio.ImeiRetrievedHandler onImeiRetrieved;

		/// <summary>
		/// Raises the <see cref="ImeiRetrieved"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>  
		/// <param name="imei">Module's International Mobile Equipment Identification number</param>
		protected virtual void OnImeiRetrieved(CellularRadio sender, string imei)
		{
			if (onImeiRetrieved == null) onImeiRetrieved = new CellularRadio.ImeiRetrievedHandler(ImeiRetrieved);
			if (Program.CheckAndInvoke(ImeiRetrieved, onImeiRetrieved, sender, imei))
			{
				ImeiRetrieved(sender, imei);
			}
		}
		#endregion

		#region Signal Strength Retrieved
		/// <summary>
		/// Represents the delegate used for the <see cref="SignalStrengthRetrieved"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>
		/// <param name="signalStrength">Strength of the signal</param>
		public delegate void SignalStrengthRetrievedHandler(CellularRadio sender, SignalStrengthType signalStrength);
		/// <summary>
		/// Event raised when the module receives a phone activity message.
		/// </summary>
		public event CellularRadio.SignalStrengthRetrievedHandler SignalStrengthRetrieved;
		private CellularRadio.SignalStrengthRetrievedHandler onSignalStrengthRetrieved;

		/// <summary>
		/// Raises the <see cref="SignalStrengthRetrieved"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>  
		/// <param name="signalStrength">Strength of the signal</param>
		protected virtual void OnSignalStrengthRetrieved(CellularRadio sender, SignalStrengthType signalStrength)
		{
			if (onSignalStrengthRetrieved == null) onSignalStrengthRetrieved = new CellularRadio.SignalStrengthRetrievedHandler(SignalStrengthRetrieved);
			if (Program.CheckAndInvoke(SignalStrengthRetrieved, onSignalStrengthRetrieved, sender, signalStrength))
			{
				SignalStrengthRetrieved(sender, signalStrength);
			}
		}
		#endregion

		#region Operator Retrieved
		/// <summary>
		/// Represents the delegate used for the <see cref="OperatorRetrieved"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>
		/// <param name="operatorName">Name of the operator to which the module is connected. It is null if the module is not connected to any operator.</param>
		public delegate void OperatorRetrievedHandler(CellularRadio sender, string operatorName);
		/// <summary>
		/// Event raised by the <see cref="RetrieveOperator"/> method.
		/// </summary>
		public event CellularRadio.OperatorRetrievedHandler OperatorRetrieved;
		private CellularRadio.OperatorRetrievedHandler onOperatorRetrieved;

		/// <summary>
		/// Raises the <see cref="OperatorRetrieved"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>  
		/// <param name="operatorName">Strength of the signal</param>
		protected virtual void OnOperatorRetrieved(CellularRadio sender, string operatorName)
		{
			if (onOperatorRetrieved == null) onOperatorRetrieved = new CellularRadio.OperatorRetrievedHandler(OperatorRetrieved);
			if (Program.CheckAndInvoke(OperatorRetrieved, onOperatorRetrieved, sender, operatorName))
			{
				OperatorRetrieved(sender, operatorName);
			}
		}
		#endregion

		#region SMS List Retrieved
		/// <summary>
		/// Represents the delegate used for the <see cref="SmsListRetrieved"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>
		/// <param name="smsList">Strength of the signal</param>
		public delegate void SmsListRetrievedHandler(CellularRadio sender, ArrayList smsList);
		/// <summary>
		/// Event raised when the module receives a phone activity message.
		/// </summary>
		public event CellularRadio.SmsListRetrievedHandler SmsListRetrieved;
		private CellularRadio.SmsListRetrievedHandler onSmsListRetrieved;

		/// <summary>
		/// Raises the <see cref="SmsListRetrieved"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>  
		/// <param name="smsList">Strength of the signal</param>
		protected virtual void OnSmsListRetrieved(CellularRadio sender, ArrayList smsList)
		{
			if (onSmsListRetrieved == null) onSmsListRetrieved = new CellularRadio.SmsListRetrievedHandler(SmsListRetrieved);
			if (Program.CheckAndInvoke(SmsListRetrieved, onSmsListRetrieved, sender, smsList))
			{
				SmsListRetrieved(sender, smsList);
			}
		}
		#endregion

		#region Call Ended
		/// <summary>
		/// Represents the delegate used for the <see cref="CallEnded"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>
		/// <param name="reason">The reason the call has ended</param>
		public delegate void CallEndedHandler(CellularRadio sender, CallEndType reason);
		/// <summary>
		/// Event raised when the module receives a phone activity message.
		/// </summary>
		public event CellularRadio.CallEndedHandler CallEnded;
		private CellularRadio.CallEndedHandler onCallEnded;

		/// <summary>
		/// Raises the <see cref="CallEnded"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>  
		/// <param name="reason">The reason the call has ended</param>
		protected virtual void OnCallEnded(CellularRadio sender, CallEndType reason)
		{
			if (onCallEnded == null) onCallEnded = new CellularRadio.CallEndedHandler(CallEnded);
			if (Program.CheckAndInvoke(CallEnded, onCallEnded, sender, reason))
			{
				CallEnded(sender, reason);
			}
		}
		#endregion

		#region Call Connected
		/// <summary>
		/// Represents the delegate used for the <see cref="CallConnected"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>
		/// <param name="number"> Number to which the module is connected</param>
		public delegate void CallConnectedHandler(CellularRadio sender, string number);
		/// <summary>
		/// Event raised when the module receives a phone activity message.
		/// </summary>
		public event CellularRadio.CallConnectedHandler CallConnected;
		private CellularRadio.CallConnectedHandler onCallConnected;

		/// <summary>
		/// Raises the <see cref="CallConnected"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>  
		/// <param name="number">Number to which the module is connected</param>
		protected virtual void OnCallConnected(CellularRadio sender, string number)
		{
			if (onCallConnected == null) onCallConnected = new CellularRadio.CallConnectedHandler(CallConnected);
			if (Program.CheckAndInvoke(CallConnected, onCallConnected, sender, number))
			{
				CallConnected(sender, number);
			}
		}
		#endregion

		#region GPRS Attached
		/// <summary>
		/// Represents the delegate used for the <see cref="GprsAttached"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>
		/// <param name="ipAddress"> Number to which the module is connected</param>
		public delegate void GprsAttachedHandler(CellularRadio sender, string ipAddress);
		/// <summary>
		/// Event raised when the module receives a phone activity message.
		/// </summary>
		public event CellularRadio.GprsAttachedHandler GprsAttached;
		private CellularRadio.GprsAttachedHandler onGprsAttached;

		/// <summary>
		/// Raises the <see cref="GprsAttached"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>  
		/// <param name="ipAddress">Number to which the module is connected</param>
		protected virtual void OnGprsAttached(CellularRadio sender, string ipAddress)
		{
			if (onGprsAttached == null) onGprsAttached = new CellularRadio.GprsAttachedHandler(GprsAttached);
			if (Program.CheckAndInvoke(GprsAttached, onGprsAttached, sender, ipAddress))
			{
				GprsAttached(sender, ipAddress);
			}
		}
		#endregion

		#region Module Initialized
		/// <summary>
		/// Represents the delegate used for the <see cref="ModuleInitialized"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>
		public delegate void ModuleInitializedHandler(CellularRadio sender);
		/// <summary>
		/// Event raised when the module receives a phone activity message.
		/// </summary>
		public event CellularRadio.ModuleInitializedHandler ModuleInitialized;
		private CellularRadio.ModuleInitializedHandler onModuleInitialized;

		/// <summary>
		/// Raises the <see cref="ModuleInitialized"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>  
		protected virtual void OnModuleInitialized(CellularRadio sender)
		{
			if (onModuleInitialized == null) onModuleInitialized = new CellularRadio.ModuleInitializedHandler(ModuleInitialized);
			if (Program.CheckAndInvoke(ModuleInitialized, onModuleInitialized, sender))
			{
				ModuleInitialized(sender);
			}
		}
		#endregion

		#region TcpDataReceived
		/// <summary>
		/// Represents the delegate used for the <see cref="TcpDataReceived"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>
		/// <param name="data">The data received from the TCP connection.</param>
		public delegate void TcpDataReceivedHandler(CellularRadio sender, string data);
		/// <summary>
		/// Event raised when the module receives Tcp data.
		/// </summary>
		public event TcpDataReceivedHandler TcpDataReceived;
		//private TcpDataReceivedHandler _onTcpDataReceived;

		/// <summary>
		/// Raises the <see cref="TcpDataReceived"/> event.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>  
		/// <param name="data">The data received from the TCP connection.</param>
		protected virtual void OnTcpDataReceived(CellularRadio sender, string data)
		{
			if (TcpDataReceived != null)
				TcpDataReceived(sender, data);
		}
		#endregion

		#endregion

		#region SMS
		/// <summary>
		/// SMS Text Message
		/// </summary>
		public class Sms
		{
			/// <summary>
			/// Number
			/// </summary>
			public string TelephoneNumber;
			/// <summary>
			/// Message content
			/// </summary>
			public string TextMessage;
			/// <summary>
			/// Status of the message
			/// </summary>
			public SmsState Status;
			/// <summary>
			/// Date and time when the message was sent or received
			/// </summary>
			public DateTime Timestamp;
			/// <summary>
			/// Index of the message in the SIM card's memory
			/// </summary>
			public int Index
			{
				get
				{
					return index;
				}
				internal set
				{
					index = value;
				}
			}
			private int index;
			/// <summary>
			/// Instantiates a new SMS with empty number, and content, marks it as unsent and with the current time as the timestamp.
			/// </summary>
			public Sms()
			{
				TelephoneNumber = "";
				TextMessage = "";
				Status = SmsState.StoredUnsent;
				Timestamp = DateTime.Now;
				Index = -1;
			}

			/// <summary>
			/// Instantiates a new SMS message with the given parameters.
			/// </summary>
			/// <param name="number">Number</param>
			/// <param name="text">Message content</param>
			/// <param name="state">Status</param>
			/// <param name="timestamp">Timestamp</param>
			public Sms(string number, string text, SmsState state, DateTime timestamp)
			{
				this.TelephoneNumber = number;
				this.TextMessage = text;
				this.Status = state;
				this.Timestamp = timestamp;
			}

			private Sms(string number, string text, SmsState state, DateTime timestamp, int index)
			{
				this.TelephoneNumber = number;
				this.TextMessage = text;
				this.Status = state;
				this.Timestamp = timestamp;
				this.Index = index;
			}

		}

		/// <summary>
		/// Send an SMS text message
		/// </summary>
		/// <param name="number">String with the telephone number</param>
		/// <param name="message">Body of the message</param>
		/// <returns></returns>
		public ReturnedState SendSms(string number, string message)
		{
			// Check if module is on
			if (!isPowerOn) return ReturnedState.ModuleIsOff;

			// Check if serial line is open
			if (serialLine.IsOpen)
			{
				//Check if module is busy
				if (isModuleBusy) return ReturnedState.ModuleBusy;
				isModuleBusy = true;

				serialLine.Write("AT+CMGS=\"+" + number + "\"\r\n");
				Thread.Sleep(100);
				serialLine.Write(message);
				Thread.Sleep(100);
				serialLine.Write((char)26 + "\r");
				//semaphore = true;
				//while (semaphore) Thread.Sleep(10);
				return ReturnedState.OK;
			}
			else return ReturnedState.Error;
		}

		/// <summary>
		/// Requests to read the SMS message in the specified position. Message is returned in the <see cref="SmsRetrieved"/> event.
		/// </summary>
		/// <param name="position">Position in memory where the message is stored</param>
		/// <param name="markAsRead">Whether unread messages will be marked as read</param>
		/// <returns></returns>
		public ReturnedState RetrieveSms(int position, bool markAsRead)
		{
			if (isModuleBusy) return ReturnedState.ModuleBusy;
			requestedMessages.Enqueue(position);
			if (markAsRead)
				return SendATCommand("AT+CMGR=" + position + ",0");
			else
				return SendATCommand("AT+CMGR=" + position + ",1");
		}

		/// <summary>
		/// Delete an SMS message
		/// </summary>
		/// <param name="position">Position in memory where the message is stored</param>
		public ReturnedState DeleteSms(int position)
		{
			return SendATCommand("AT+CMGD=" + position);
		}

		/// <summary>
		/// Requests to get all SMS in a given state or all of them.
		/// </summary>
		/// <param name="state"></param>
		/// <returns></returns>
		public ReturnedState RetrieveSmsList(SmsState state)
		{
			switch (state)
			{
				case SmsState.All:
					return SendATCommand("AT+CMGL=\"ALL\"");
				case SmsState.ReceivedRead:
					return SendATCommand("AT+CMGL=\"REC READ\"");
				case SmsState.ReceivedUnread:
					return SendATCommand("AT+CMGL=\"REC UNREAD\"");
				case SmsState.StoredSent:
					return SendATCommand("AT+CMGL=\"STO SENT\"");
				case SmsState.StoredUnsent:
					return SendATCommand("AT+CMGL=\"STO UNSENT\"");
				default:
					return ReturnedState.InvalidCommand;
			}

		}

		/// <summary>
		/// Deletes all SMS messages stored in the SIM card
		/// </summary>
		/// <returns></returns>
		public ReturnedState DeleteAllSms()
		{
			return SendATCommand("AT+CMGD=0,4");
		}
		#endregion

		#region VOICE CALLS
		/// <summary>
		/// Picks up an incoming voice call
		/// </summary>
		/// <returns></returns>
		public ReturnedState PickUp()
		{
			return SendATCommand("ATA");
		}

		/// <summary>
		/// Hangs up an active call
		/// </summary>
		/// <returns></returns>
		public ReturnedState HangUp()
		{
			return SendATCommand("ATH");
		}

		/// <summary>
		/// Dials a number in order to start a voice call
		/// </summary>
		/// <param name="number">Number to be called</param>
		/// <returns></returns>
		public ReturnedState Dial(string number)
		{
			ReturnedState ret;
			if (number.IndexOf("+") >= 0)
				ret = SendATCommand("ATD" + number + ";");
			else
				ret = SendATCommand("ATD+" + number + ";");
			if (ret == ReturnedState.OK)
			{
				isModuleBusy = true;
			}
			return ret;
		}

		/// <summary>
		/// Redials the last number dialled
		/// </summary>
		/// <returns></returns>
		public ReturnedState Redial()
		{
			return SendATCommand("ATDL");
		}

		/// <summary>
		/// Raises the <see cref="PhoneActivityRetrieved"/> event, which contains the activity in which the phone is currently engaged.
		/// </summary>
		/// <returns></returns>
		public ReturnedState RetrievePhoneActivity()
		{
			return SendATCommand("AT+CPAS");
		}
		#endregion

		#region PHONEBOOK
		/// <summary>
		/// Entry of the phonebook
		/// </summary>
		public class Contact
		{
			/// <summary>
			/// String with the telephone number
			/// </summary>
			public string telephoneNumber;
			/// <summary>
			/// Name of the entry
			/// </summary>
			public string name;
			/// <summary>
			/// Instantiates a Phonebook entry from a number and a name
			/// </summary>
			/// <param name="telephoneNumber"></param>
			/// <param name="name"></param>
			public Contact(string telephoneNumber, string name)
			{
				this.telephoneNumber = telephoneNumber;
				this.name = name;
			}
		}

		/// <summary>
		/// Adds an entry to the SIM card phonebook
		/// </summary>
		/// <param name="number">Telephone number</param>
		/// <param name="name">Name</param>
		public ReturnedState SaveContact(string number, string name)
		{
			return SendATCommand("AT +CPBW=,\"" + number + "\", ,\"" + name + "\"");
		}

		/// <summary>
		/// Adds an entry to the SIM card phonebook
		/// </summary>
		/// <param name="contact">Contact object containing the number and name</param>
		public ReturnedState SaveContact(Contact contact)
		{
			if (contact == null)
				return ReturnedState.Error;
			else
				return SaveContact(contact.telephoneNumber, contact.name);
		}

		/// <summary>
		/// Adds an entry to the SIM card phonebook
		/// </summary>
		/// <param name="index">Index of the entry where the contact is going to be stored</param>
		/// <param name="number">Number</param>
		/// <param name="name">Name</param>
		/// <returns></returns>
		public ReturnedState SaveContact(int index, string number, string name)
		{
			return SendATCommand("AT+CPBW=" + index + ",\"" + number + "\", ,\"" + name + "\"");
		}

		/// <summary>
		/// Adds an entry to the SIM card phonebook
		/// </summary>
		/// <param name="index">Index of the entry where the contact is going to be stored</param>
		/// <param name="contact">Contact object containing the number and name</param>
		/// <returns></returns>
		public ReturnedState SaveContact(int index, Contact contact)
		{
			if (contact == null)
				return ReturnedState.Error;
			else
				return SaveContact(index, contact.telephoneNumber, contact.name);
		}

		/// <summary>
		/// Raises the <see cref="ContactRetrieved"/> event, which contains the contact stored in the specified position.
		/// </summary>
		/// <param name="index">Index of the phonebook entry where the contact is stored</param>
		/// <returns></returns>
		public ReturnedState RetrieveContact(int index)
		{
			return SendATCommand("AT+CPBR=" + index);
		}

		/// <summary>
		/// Delete the contact at the specified position
		/// </summary>
		/// <param name="index">Index of the phonebook entry where the contact is stored</param>
		/// <returns></returns>
		public ReturnedState DeleteContact(int index)
		{
			return SendATCommand("AT+CPBW=" + index);
		}
		#endregion

		#region TIME
		/// <summary>
		/// Sends a request to get the module's date and time
		/// </summary>
		/// <returns></returns>
		public ReturnedState RetrieveClock()
		{
			return SendATCommand("AT+CCLK?");
		}

		/// <summary>
		/// Sets the module's internal date and time
		/// </summary>
		/// <param name="clock"></param>
		/// <returns></returns>
		public ReturnedState SetClock(DateTime clock)
		{
			// Convert dateTime to format: +CCLK: “YY/MM/DD,HH:MM:SS+02”
			//string ret = SendATCommandAndGetResponse("AT+CCLK=\"" + dateTime.ToString("YY") +  + "\"");
			string command;
			command = "AT+CCLK=\"" +
				clock.ToString("yy") + "/" +
				clock.ToString("MM") + "/" +
				clock.ToString("dd") + "," +
				clock.ToString("HH:mm:ss") + "+00\"";
			return SendATCommand(command);
		}
		#endregion

		#region GENERAL ENQUIRIES
		/// <summary>
		/// Raises the <see cref="ImeiRetrieved"/> event which contains the module international mobile equipment identification number.
		/// </summary>
		/// <returns></returns>
		public ReturnedState RetrieveImei()
		{
			return SendATCommand("AT+GSN");
		}

		/// <summary>
		/// Resets module
		/// </summary>
		public void Reset()
		{
			PowerOff();
			PowerOn(2);
		}


		/// <summary>
		/// Raises the <see cref="SignalStrengthRetrieved"/> event which contains the strength of the signal.
		/// </summary>
		/// <returns></returns>
		public ReturnedState RetrieveSignalStrength()
		{
			return SendATCommand("AT+CSQ");
		}

		/// <summary>
		/// Raises the <see cref="OperatorRetrieved"/> event which contains the name of the operator, if the module is connected to a network.
		/// </summary>
		/// <returns></returns>
		public ReturnedState RetrieveOperator()
		{
			return SendATCommand("AT+COPS?");
		}

		/// <summary>
		/// Raises the <see cref="PinStateRetrieved"/> event which contains the current state of the PIN.
		/// </summary>
		/// <returns></returns>
		public ReturnedState RetrievePinState()
		{
			return SendATCommand("AT+CPIN?");
		}
		#endregion

		#region GPRS

		/// <summary>
		/// Attach GPRS
		/// </summary>
		/// <param name="accessPointName"></param>
		/// <param name="username"></param>
		/// <param name="password"></param>
		/// <returns></returns>
		public ReturnedState AttachGPRS(string accessPointName, string username, string password)
		{
			if (isModuleBusy) return ReturnedState.ModuleBusy;
			SendATCommand("AT+CSTT=\"" + accessPointName + "\",\"" + username + "\",\"" + password + "\"");

			SendATCommand("AT+CIICR");
			Thread.Sleep(3000);

			return SendATCommand("AT+CIFSR");
		}

		/// <summary>
		/// Detach GPRS
		/// </summary>
		/// <returns></returns>
		public ReturnedState DetachGprs()
		{
			return SendATCommand("AT+CGATT=0");
		}

		/// <summary>
		/// Sends data over a TCP connection
		/// </summary>
		/// <param name="data">Data to be sent</param>
		/// <returns></returns>
		[Obsolete("Use the new TCP functions. This stub left for interface compatibility with original Seeed driver.", true)]
		public ReturnedState SendTcpData(string data)
		{
			throw new NotImplementedException("Obsolete");
			SendATCommand("AT+CIPSEND");
			Thread.Sleep(1000);
			return SendRawTcpData(data + (char)26);
		}
		#endregion

		#region TCP
		private bool _isTcpConnectionAtResultReceived;
		private bool _tcpConnectResultSuccess;
		private Timer _connectTcpTimeoutTimer = new Timer(1, Timer.BehaviorType.RunOnce);
		/// <summary>
		/// Connects to a TCP server
		/// </summary>
		/// <param name="server">IP address of the server</param>
		/// <param name="port">Port in the server</param>
		/// <param name="timeout">How long to wait before the connection attempt is aborted</param>
		/// <returns></returns>
		public ReturnedState ConnectTCP(string server, int port, TimeSpan timeout)
		{
			//			_connectTcpTimeoutTimer.Tick += ConnectTcpTimeoutTimerTick;
			//			_connectTcpTimeoutTimer.Interval = timeout;
			//			_connectTcpTimeoutTimer.Start();
			_isTcpConnectionAtResultReceived = false;
			var result = SendATCommand("AT+CIPSTART=\"TCP\",\"" + server + "\"," + port);
			while (!_isTcpConnectionAtResultReceived) { /*wat for SIM900 response*/ }
			//			_connectTcpTimeoutTimer.Tick -= ConnectTcpTimeoutTimerTick;
			if (_tcpConnectResultSuccess)
			{
				DebugPrint("TCP Connected");
				return result;
			}
			return ReturnedState.Error;
		}

		private void ConnectTcpTimeoutTimerTick(Timer timer)
		{
			_isTcpConnectionAtResultReceived = true;
		}

		/// <summary>
		/// Disconnects from TCP server
		/// </summary>
		/// <returns></returns>
		public ReturnedState DisconnectTCP()
		{
			return SendATCommand("AT+CIPCLOSE");
		}

		/// <summary>
		/// Configure the module as a TCP server
		/// </summary>
		/// <param name="port">Port</param>
		/// <returns></returns>
		public ReturnedState ConfigureTCPServer(int port)
		{
			return SendATCommand("AT+CIPSERVER=1," + port);
		}


		#endregion
	}
}
